# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

dd

"""
winapi.py
By: Denver Coneybeare <denver@sleepydragon.org>
Oct 26, 2012

This module defines C functions from the Windows API that are
used by the winnotify package.  This module is not typically imported directly,
but its low-level calls are used throughout this package.

The related "constants" are defined in the winnotify.winapi_constants module,
allowing them to be used without having to use this module.  This allows easily
replacing this module with a fake version during unit testing, even allowing
unit tests to run on non-Windows platforms.
"""

import ctypes.wintypes

################################################################################

class GUID(ctypes.Structure):
    """
    The Windows GUID structure, defined in Guiddef.h
    """

    _fields_ = (
        ("Data1", ctypes.c_ulong),
        ("Data2", ctypes.c_ushort),
        ("Data3", ctypes.c_ushort),
        ("Data4", ctypes.c_byte * 8),
    )

    def from_str(self, s):
        """
        Sets the fields of this structure from a 16-byte string.
        *s* must be a byte string of length exactly 16.
        Raises AssertionError if the "s" argument is not a string or has a
        length other than 16.
        """
        assert isinstance(s, str)
        assert len(s) == 16
        self.Data1 = (ord(s[0]) | ord(s[1]) << 8 | ord(s[2]) << 16
            | ord(s[3]) << 24)
        self.Data2 = ord(s[4]) | ord(s[5]) << 8
        self.Data3 = ord(s[6]) | ord(s[7]) << 8
        for i in range(8):
            self.Data4[i] = ord(s[i + 8])

################################################################################

class TimeoutVersionUnion(ctypes.Union):
    """
    The union used in the NOTIFYICONDATA structure for uTimeout and uVersion.
    """
    _fields_ = (
        ("uTimeout", ctypes.wintypes.UINT),
        ("uVersion", ctypes.wintypes.UINT),
    )

class NOTIFYICONDATA(ctypes.Structure):
    """
    The NOTIFYICONDATA structure defined in ShellAPI.h
    http://msdn.microsoft.com/en-us/library/windows/desktop/bb773352%28v=vs.85%29.aspx
    """

    # the lengths of some of the fields of this structure, in characters
    SZTIP_NUM_CHARS = 128
    SZINFO_NUM_CHARS = 256
    SZINFOTITLE_NUM_CHARS = 64

    # the TimeoutVersionUnion is set to be anonymous so that the elements of
    # the union may be assigned by setting them directly on this object
    _anonymous_ = (
        "TimeoutVersionUnion",
    )

# we assign the _fields_ attribute of the NOTIFYICONDATA structure after the
# fact so that we can reference the SZTIP_NUM_CHARS, SZINFO_NUM_CHARS, and
# SZINFOTITLE_NUM_CHARS attributes of the NOTIFYICONDATA class
NOTIFYICONDATA._fields_ = (
    ("cbSize", ctypes.wintypes.DWORD),
    ("hWnd", ctypes.wintypes.HWND),
    ("uID", ctypes.wintypes.UINT),
    ("uFlags", ctypes.wintypes.UINT),
    ("uCallbackMessage", ctypes.wintypes.UINT),
    ("hIcon", ctypes.wintypes.HICON),
    ("szTip", ctypes.c_wchar * NOTIFYICONDATA.SZTIP_NUM_CHARS),
    ("dwState", ctypes.wintypes.DWORD),
    ("dwStateMask", ctypes.wintypes.DWORD),
    ("szInfo", ctypes.c_wchar * NOTIFYICONDATA.SZINFO_NUM_CHARS),
    ("TimeoutVersionUnion", TimeoutVersionUnion),
    ("szInfoTitle", ctypes.c_wchar * NOTIFYICONDATA.SZINFOTITLE_NUM_CHARS),
    ("dwInfoFlags", ctypes.wintypes.DWORD),
    ("guidItem", GUID),
    ("hBalloonIcon", ctypes.wintypes.HICON),
)

# the size (in bytes) of the NOTIFYICONDATA structure
NOTIFYICONDATA_SIZE = ctypes.sizeof(NOTIFYICONDATA)

################################################################################

class SHSTOCKICONINFO(ctypes.Structure):
    """
    The SHSTOCKICONINFO structure, defined in ShellAPI.h.
    http://msdn.microsoft.com/en-us/library/windows/desktop/bb759805%28v=vs.85%29.aspx
    """

    # the length of the SZPATH_NUM_CHARS field of this structure, in characters
    SZPATH_NUM_CHARS = 260 # 260 == MAX_PATH

# we assign the _fields_ attribute of the SHSTOCKICONINFO structure after the
# fact so that we can reference the SZPATH_NUM_CHARS attribute of the
# SHSTOCKICONINFO class
SHSTOCKICONINFO._fields_ = (
    ("cbSize", ctypes.wintypes.DWORD),
    ("hIcon", ctypes.wintypes.HICON),
    ("iSysImageIndex", ctypes.c_int),
    ("iIcon", ctypes.c_int),
    ("szPath", ctypes.wintypes.WCHAR * SHSTOCKICONINFO.SZPATH_NUM_CHARS),
)

# the size (in bytes) of the SHSTOCKICONINFO structure
SHSTOCKICONINFO_SIZE = ctypes.sizeof(SHSTOCKICONINFO)

################################################################################

class MSG(ctypes.Structure):
    """
    The MSG structure, defined in WinUser.h.
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms644958%28v=vs.85%29.aspx
    """

    _fields_ = (
        ("hwnd", ctypes.wintypes.HWND),
        ("message", ctypes.wintypes.UINT),
        ("wParam", ctypes.wintypes.WPARAM),
        ("lParam", ctypes.wintypes.LPARAM),
        ("time", ctypes.wintypes.DWORD),
        ("pt", ctypes.wintypes.POINT),
    )

################################################################################

# defined in WinUser.h
# factory function that wraps Python functions into a form that can be passed
# as a parameter to C functions to be called back
WNDPROC = ctypes.WINFUNCTYPE(
    ctypes.c_long, # LRESULT return type
    ctypes.wintypes.HWND,
    ctypes.wintypes.UINT,
    ctypes.wintypes.WPARAM,
    ctypes.wintypes.LPARAM,
)

################################################################################

class WNDCLASSEX(ctypes.Structure):
    """
    The WNDCLASSEX structure, defined in WinUser.h.
    Instances of this struct are specified to RegisterClassEx().
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms633577%28v=vs.85%29.aspx
    """

    _fields_ = (
        ("cbSize", ctypes.wintypes.UINT),
        ("style", ctypes.wintypes.UINT),
        ("lpfnWndProc", WNDPROC),
        ("cbClsExtra", ctypes.c_int),
        ("cbWndExtra", ctypes.c_int),
        ("hInstance", ctypes.wintypes.HINSTANCE),
        ("hIcon", ctypes.wintypes.HICON),
        ("hCursor", ctypes.wintypes.HICON),
        ("hbrBackground", ctypes.wintypes.HBRUSH),
        ("lpszMenuName", ctypes.wintypes.LPCWSTR),
        ("lpszClassName", ctypes.wintypes.LPCWSTR),
        ("hIconSm", ctypes.wintypes.HICON),
    )

# the size (in bytes) of the WNDCLASSEX_SIZE structure
WNDCLASSEX_SIZE = ctypes.sizeof(WNDCLASSEX)

################################################################################
# CreateWindowEx() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.HWND,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.LPCWSTR,
    ctypes.wintypes.LPCWSTR,
    ctypes.wintypes.DWORD,
    ctypes.c_int,
    ctypes.c_int,
    ctypes.c_int,
    ctypes.c_int,
    ctypes.wintypes.HWND,
    ctypes.wintypes.HMENU,
    ctypes.wintypes.HINSTANCE,
    ctypes.wintypes.LPVOID,
)
paramflags = (
    (1, "dwExStyle"),
    (1, "lpClassName", None),
    (1, "lpWindowName", None),
    (1, "dwStyle"),
    (1, "X"),
    (1, "Y"),
    (1, "nWidth"),
    (1, "nHeight"),
    (1, "hWndParent", 0),
    (1, "hMenu", 0),
    (1, "hInstance", 0),
    (1, "lpParam", None),
)
CreateWindowEx = prototype(("CreateWindowExW", ctypes.windll.user32), paramflags)

################################################################################
# DestroyWindow() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms632682%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.HWND,
)
paramflags = (
    (1, "hWnd"),
)
DestroyWindow = prototype(("DestroyWindow", ctypes.windll.user32), paramflags)

################################################################################
# DestroyWindow() defined in ShellAPI.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/bb762159%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.DWORD,
    ctypes.POINTER(NOTIFYICONDATA),
)
paramflags = (
    (1, "dwMessage"),
    (1, "lpdata"),
)
Shell_NotifyIcon = prototype(("Shell_NotifyIconW", ctypes.windll.shell32), paramflags)

################################################################################
# SHGetStockIconInfo() defined in ShellAPI.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/bb762205%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.HRESULT,
    ctypes.c_int,
    ctypes.wintypes.UINT,
    ctypes.POINTER(SHSTOCKICONINFO),
)
paramflags = (
    (1, "siid"),
    (1, "uFlags"),
    (1, "psii"),
)
SHGetStockIconInfo = prototype(("SHGetStockIconInfo", ctypes.windll.shell32), paramflags)

################################################################################
# LoadImage() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms648045%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.HANDLE,
    ctypes.wintypes.HINSTANCE,
    ctypes.wintypes.LPCWSTR,
    ctypes.wintypes.UINT,
    ctypes.c_int,
    ctypes.c_int,
    ctypes.wintypes.UINT,
)
paramflags = (
    (1, "hinst", 0),
    (1, "lpszName"),
    (1, "uType"),
    (1, "cxDesired"),
    (1, "cyDesired"),
    (1, "fuLoad"),
)
LoadImage = prototype(("LoadImageW", ctypes.windll.user32), paramflags)

################################################################################
# DeleteObject() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/dd183539%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.HGDIOBJ,
)
paramflags = (
    (1, "hObject"),
)
DeleteObject = prototype(("DeleteObject", ctypes.windll.gdi32), paramflags)

################################################################################
# DestroyCursor() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms648386%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.HICON,
)
paramflags = (
    (1, "hCursor"),
)
DestroyCursor = prototype(("DestroyCursor", ctypes.windll.user32), paramflags)

################################################################################
# DestroyIcon() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms648063%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.HICON,
)
paramflags = (
    (1, "hIcon"),
)
DestroyIcon = prototype(("DestroyIcon", ctypes.windll.user32), paramflags)

################################################################################
# GetMessage() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms644936%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.POINTER(MSG),
    ctypes.wintypes.HWND,
    ctypes.wintypes.UINT,
    ctypes.wintypes.UINT,
)
paramflags = (
    (1, "lpMsg"),
    (1, "hWnd"),
    (1, "wMsgFilterMin"),
    (1, "wMsgFilterMax"),
)
GetMessage = prototype(("GetMessageW", ctypes.windll.user32), paramflags)

################################################################################
# TranslateMessage() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms644955%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.POINTER(MSG),
)
paramflags = (
    (1, "lpMsg"),
)
TranslateMessage = prototype(("TranslateMessage", ctypes.windll.user32), paramflags)

################################################################################
# DispatchMessage() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms644934%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.c_long, #LRESULT
    ctypes.POINTER(MSG),
)
DispatchMessage_paramflags = (
    (1, "lpMsg"),
)
DispatchMessage = prototype(("DispatchMessageW", ctypes.windll.user32), paramflags)

################################################################################
# PostQuitMessage() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms644945%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    None,
    ctypes.c_int,
)
paramflags = (
    (1, "nExitCode", 0),
)
PostQuitMessage = prototype(("PostQuitMessage", ctypes.windll.user32), paramflags)

################################################################################
# DefWindowProc() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms633572%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.c_long, # LRESULT
    ctypes.wintypes.HWND,
    ctypes.wintypes.UINT,
    ctypes.wintypes.WPARAM,
    ctypes.wintypes.LPARAM,
)
paramflags = (
    (1, "hWnd"),
    (1, "Msg"),
    (1, "wParam"),
    (1, "lParam"),
)
DefWindowProc = prototype(("DefWindowProcW", ctypes.windll.user32), paramflags)

################################################################################
# GetCurrentThreadId() defined in WinBase.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms683183%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.DWORD,
)
paramflags = (
)
GetCurrentThreadId = prototype(("GetCurrentThreadId", ctypes.windll.kernel32), paramflags)

################################################################################
# PostThreadMessage() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms644946%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.UINT,
    ctypes.wintypes.WPARAM,
    ctypes.wintypes.LPARAM,
)
paramflags = (
    (1, "idThread"),
    (1, "Msg"),
    (1, "wParam", 0),
    (1, "lParam", 0),
)
PostThreadMessage = prototype(("PostThreadMessageW", ctypes.windll.user32), paramflags)

################################################################################
# RegisterClassEx() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms633587%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.ATOM,
    ctypes.POINTER(WNDCLASSEX)
)
paramflags = (
    (1, "lpwcx"),
)
RegisterClassEx = prototype(("RegisterClassExW", ctypes.windll.user32), paramflags)

################################################################################
# UnregisterClass() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms644899%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.c_bool,
    ctypes.wintypes.LPCWSTR,
    ctypes.wintypes.HINSTANCE,
)
paramflags = (
    (1, "lpClassName"),
    (1, "hInstance"),
)
UnregisterClass = prototype(("UnregisterClassW", ctypes.windll.user32), paramflags)

################################################################################
# GetModuleHandle() defined in WinBase.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms683199%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.HMODULE,
    ctypes.wintypes.LPCWSTR,
)
paramflags = (
    (1, "lpModuleName", None),
)
GetModuleHandle = prototype(("GetModuleHandleW", ctypes.windll.kernel32), paramflags)

################################################################################
# CreatePopupMenu() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms647626%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.HMENU,
)
paramflags = (
)
CreatePopupMenu = prototype(("CreatePopupMenu", ctypes.windll.user32), paramflags)

################################################################################
# DestroyMenu() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms647631%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.HMENU,
)
paramflags = (
    (1, "hMenu"),
)
DestroyMenu = prototype(("DestroyMenu", ctypes.windll.user32), paramflags)

################################################################################
# AppendMenu() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms647616%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.HMENU,
    ctypes.wintypes.UINT,
    ctypes.POINTER(ctypes.wintypes.UINT),
    ctypes.wintypes.LPCWSTR,
)
paramflags = (
    (1, "hMenu"),
    (1, "uFlags"),
    (1, "uIDNewItem"),
    (1, "lpNewItem"),
)
AppendMenu = prototype(("AppendMenuW", ctypes.windll.user32), paramflags)

################################################################################
# GetSubMenu() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms647984%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.HMENU,
    ctypes.wintypes.HMENU,
    ctypes.c_int,
)
paramflags = (
    (1, "hMenu"),
    (1, "nPos"),
)
GetSubMenu = prototype(("GetSubMenu", ctypes.windll.user32), paramflags)

################################################################################
# TrackPopupMenuEx() defined in WinUser.h
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms648003%28v=vs.85%29.aspx

prototype = ctypes.WINFUNCTYPE(
    ctypes.wintypes.BOOL,
    ctypes.wintypes.HMENU,
    ctypes.wintypes.UINT,
    ctypes.c_int,
    ctypes.c_int,
    ctypes.wintypes.HWND,
    ctypes.wintypes.LPCWSTR,
)
paramflags = (
    (1, "hmenu"),
    (1, "fuFlags"),
    (1, "x"),
    (1, "y"),
    (1, "hwnd"),
    (1, "lptpm"),
)
TrackPopupMenuEx = prototype(("TrackPopupMenuEx", ctypes.windll.user32), paramflags)

################################################################################

def get_last_error(self):
    """
    Returns the error number and message for the error returned from
    GetLastError().  Returns the tuple (errno, message) where "errno" is an
    int whose value is the Windows error number and "message" is a Unicode
    string whose value is the localized message corresponding to the errno.
    """
    e = ctypes.WinError()
    errno = e.winerror
    message = e.strerror
    return (errno, message)

################################################################################

def MAKEINTRESOURCE(value):
    """
    Converts an int into an "integer resource" the same way that MAKEINTRESOURCE
    works.  The returned object may be specified to C methods that defined their
    parameter to be a ctypes.wintypes.LPCWSTR object.
    *value* must be an int whose value to convert into an int resource.
    Raises AssertionError if the given value is not an int.
    """
    assert isinstance(value, int)
    int_resource = ctypes.cast(value, ctypes.wintypes.LPCWSTR)
    return int_resource

################################################################################
# clean up some no-longer-needed objects so they can be garbage collected

del TimeoutVersionUnion
del prototype
del paramflags
